﻿using System;
using System.Collections.Generic;
using MyRoguelike.Game.Objects;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MyRoguelike.Tools;

namespace MyRoguelike.Game
{
    struct MapBlock
    {
        // Block type
        public Level.EMapBlockType Type;

        // Block illumination (alpha for drawing block and objects)
        public float FogOfWar;
    }

    class Level
    {
        private List<GameObject> _lstObjects = new List<GameObject>();

        private MapBlock[,] _map;
        private GOCamera _camera;
        private GOPlayer _player;
        private Point _mapSize;

        public Point MapSize
        {
            get { return _mapSize; }
        }
        public GOPlayer Player
        {
            get { return _player; }
        }
        public GOCamera Camera
        {
            get { return _camera; }
        }
        public MapBlock[,] Map
        {
            get { return _map; }
        }
        public List<GameObject> GameObjects
        {
            get { return _lstObjects; }
        }


        // Map block type
        public enum EMapBlockType
        {
            None,
            Floor,
            Wall
        }


        // Map block size
        public const int BLOCK_SIZE = 24;

        /// <summary>
        /// Initialize level
        /// </summary>
        public void Initialize()
        {
            // Create camera
            _camera = new GOCamera(this);

            // Create player
            _player = new GOPlayer(this);
        }

        /// <summary>
        /// Generation level
        /// </summary>
        /// <param name="width">Width</param>
        /// <param name="height">Height</param>
        public void Generation(int width, int height)
        {
            _mapSize = new Point(width, height);
            _map = new MapBlock[width, height];

            // Clear map
            for (int i = 0; i < width; i++)
                for (int j = 0; j < height; j++)
                {
                    _map[i, j].Type = EMapBlockType.None;
                    _map[i, j].FogOfWar = 1f;
                }

            // Create list of room positions
            List<Point> lstRooms = new List<Point>();

            // Create rooms
            Random rnd = new Random();
            int numOfRooms = rnd.Next(12) + 3;

            for (int i = 0; i < numOfRooms; i++)
            {
                // Set radius
                int roomRadius = rnd.Next(5) + 5;

                // Set position
                Point pos = new Point(rnd.Next(width), rnd.Next(height));
                while (((pos.X - roomRadius) <= 0)
                        || ((pos.X + roomRadius) >= (width - 1))
                        || ((pos.Y - roomRadius) <= 0)
                        || ((pos.Y + roomRadius) >= (height - 1)))
                {
                    pos = new Point(rnd.Next(width), rnd.Next(height));
                }

                // Create room
                for (int x = pos.X - roomRadius; x < pos.X + roomRadius; x++)
                    for (int y = pos.Y - roomRadius; y < pos.Y + roomRadius; y++)
                        _map[x, y].Type = EMapBlockType.Floor;

                // Add room
                lstRooms.Add(pos);
            }

            // Create roads
            const int DIR_LEFT = 0;
            const int DIR_TOP = 1;
            const int DIR_RIGHT = 2;
            const int DIR_BOTTOM = 3;

            for (int i = 0; i < lstRooms.Count; i++)
            {
                Point roomPos = lstRooms[i];
                Point drawRoadPos = roomPos;

                // Get next room pos
                Point nextRoomPos = new Point();
                if ((i + 1) < lstRooms.Count)
                    nextRoomPos = lstRooms[i + 1];
                else
                    break;

                // Draw road
                while (drawRoadPos != nextRoomPos)
                {
                    // Get available directions
                    List<int> lstDirs = new List<int>();

                    if (drawRoadPos.X < nextRoomPos.X)
                        lstDirs.Add(DIR_RIGHT);
                    else if (drawRoadPos.X > nextRoomPos.X)
                        lstDirs.Add(DIR_LEFT);

                    if (drawRoadPos.Y < nextRoomPos.Y)
                        lstDirs.Add(DIR_BOTTOM);
                    else if (drawRoadPos.Y > nextRoomPos.Y)
                        lstDirs.Add(DIR_TOP);

                    // Select random direction
                    if (lstDirs.Count != 0)
                    {
                        switch (lstDirs[rnd.Next(lstDirs.Count)])
                        {
                            case DIR_LEFT:
                                drawRoadPos.X -= 1;
                                break;

                            case DIR_TOP:
                                drawRoadPos.Y -= 1;
                                break;

                            case DIR_RIGHT:
                                drawRoadPos.X += 1;
                                break;

                            case DIR_BOTTOM:
                                drawRoadPos.Y += 1;
                                break;
                        }

                        // Set block as floor
                        _map[drawRoadPos.X, drawRoadPos.Y].Type = EMapBlockType.Floor;
                    }
                }
            }

            // Create walls
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    if (_map[i, j].Type == EMapBlockType.Floor)
                    {
                        // Left
                        if (_map[i - 1, j].Type == EMapBlockType.None)
                            _map[i - 1, j].Type = EMapBlockType.Wall;

                        // Top
                        if (_map[i, j - 1].Type == EMapBlockType.None)
                            _map[i, j - 1].Type = EMapBlockType.Wall;

                        // Right
                        if (_map[i + 1, j].Type == EMapBlockType.None)
                            _map[i + 1, j].Type = EMapBlockType.Wall;

                        // Bottom
                        if (_map[i, j + 1].Type == EMapBlockType.None)
                            _map[i, j + 1].Type = EMapBlockType.Wall;
                    }
                }
            }

            // Spawn player
            Point spawnPos = new Point(rnd.Next(width), rnd.Next(height));
            while (_map[spawnPos.X, spawnPos.Y].Type != EMapBlockType.Floor)
                spawnPos = new Point(rnd.Next(width), rnd.Next(height));

            _player.Position = ConvertMapToObjectPos(spawnPos);

            // Set camera position to player position
            _camera.LookAt(_player);

            // Update fog of war
            UpdateFogOfWar();

            //Test: spawn some skeletons
            for (int i = 0; i < 10; i++)
            {
                GOSkeleton skeleton = new GOSkeleton(this);

                Point pos = new Point(rnd.Next(width), rnd.Next(height));
                while (_map[pos.X, pos.Y].Type != EMapBlockType.Floor)
                    pos = new Point(rnd.Next(width), rnd.Next(height));

                skeleton.Position = ConvertMapToObjectPos(pos);

                _lstObjects.Add(skeleton);
            }
        }

        /// <summary>
        /// Convert map coordinate to object position
        /// </summary>
        /// <param name="coord">Map coordinate</param>
        /// <returns>Object position</returns>
        public Vector2 ConvertMapToObjectPos(Point coord)
        {
            Vector2 result = new Vector2(
                (coord.X * BLOCK_SIZE) + (BLOCK_SIZE / 2),
                (coord.Y * BLOCK_SIZE) + (BLOCK_SIZE / 2));

            return result;
        }

        /// <summary>
        /// Returns value of fog of war from object position
        /// </summary>
        /// <param name="pos">Position</param>
        /// <returns>Value of fog of war</returns>
        public float GetFogOfWarFromObjectPos(Vector2 pos)
        {
            Point coord = new Point((int)(pos.X - (BLOCK_SIZE / 2)), (int)(pos.Y - (BLOCK_SIZE / 2)));
            coord.X /= BLOCK_SIZE;
            coord.Y /= BLOCK_SIZE;

            return _map[coord.X, coord.Y].FogOfWar;
        }

        /// <summary>
        /// Returns object coords
        /// </summary>
        /// <param name="gameObject">GameObject</param>
        /// <returns>Coords</returns>
        public Point GetObjectCoord(GameObject gameObject)
        {
            Vector2 pos = gameObject.Position;

            Point result = new Point((int)(pos.X - (BLOCK_SIZE / 2)), (int)(pos.Y - (BLOCK_SIZE / 2)));
            result.X /= BLOCK_SIZE;
            result.Y /= BLOCK_SIZE;

            return result;
        }

        /// <summary>
        /// Returns object from map coordinate
        /// </summary>
        /// <param name="coord">Coordinate</param>
        /// <returns>Object</returns>
        public GameObject GetObjectFromMapCoord(Point coord)
        {
            foreach (GameObject gameObject in _lstObjects)
                if (GetObjectCoord(gameObject) == coord)
                    return gameObject;

            if (GetObjectCoord(_player) == coord)
                return _player;

            return null;
        }

        /// <summary>
        /// Move object to specified point
        /// </summary>
        /// <param name="gameObject">Object</param>
        /// <param name="coord">Coordinate</param>
        /// <returns>If move is complete that returns 'true'</returns>
        public bool MoveTo(GameObject gameObject, Point coord)
        {
            bool isMove = false;

            if ((GetObjectCoord(gameObject) != coord)
                && (_map[coord.X, coord.Y].Type == EMapBlockType.Floor))
            {
                // Check collision
                GameObject otherObject = GetObjectFromMapCoord(coord);
                if (otherObject != null)
                {
                    // Call collision methods for objects
                    isMove = gameObject.OnCollision(otherObject);
                    otherObject.OnCollision(gameObject);
                }
                else isMove = true;

                // If move is allowed that replace specified object
                if (isMove)
                    gameObject.Position = ConvertMapToObjectPos(coord);
            }

            return isMove;
        }

        public void Update(GameTime gameTime)
        {
            // Update camera
            _camera.Update(gameTime);

            // Update objects
            foreach (GameObject gameObject in _lstObjects)
                gameObject.Update(gameTime);

            // Update player
            _player.Update(gameTime);
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            // Draw map
            DrawMap(spriteBatch);

            // Draw objects
            foreach (GameObject gameObject in _lstObjects)
                gameObject.Draw(spriteBatch);

            // Draw player
            _player.Draw(spriteBatch);
        }


        /// <summary>
        /// Level step
        /// </summary>
        public void Step()
        {
            // Player step
            _player.Step();

            // Camera step
            _camera.Step();

            // Step for all objects
            foreach (GameObject gameObject in _lstObjects)
                gameObject.Step();

            // Update fog of war
            UpdateFogOfWar();
        }


        /// <summary>
        /// Draw level map
        /// </summary>
        /// <param name="spriteBatch">SpriteBatch</param>
        private void DrawMap(SpriteBatch spriteBatch)
        {
            for (int i = 0; i < _mapSize.X; i++)
            {
                for (int j = 0; j < _mapSize.Y; j++)
                {
                    // Get draw position
                    Vector2 pos = new Vector2(
                                      i*BLOCK_SIZE,
                                      j * BLOCK_SIZE) - _camera.GetDrawOffset();

                    // Get texture
                    Texture2D tex = null;

                    switch (_map[i, j].Type)
                    {
                        case EMapBlockType.Floor:
                            tex = ResourceManager.Textures["Floor"];
                            break;

                        case EMapBlockType.Wall:
                            tex = ResourceManager.Textures["Wall"];
                            break;
                    }

                    // Draw texture
                    if (tex != null)
                        spriteBatch.Draw(tex, pos, Color.White * (1 - _map[i, j].FogOfWar));
                }
            }

        }

        /// <summary>
        /// Update fog of war
        /// </summary>
        private void UpdateFogOfWar()
        {
            for (int i = 0; i < _mapSize.X; i++)
            {
                for (int j = 0; j < _mapSize.Y; j++)
                {
                    if ((_map[i, j].Type != EMapBlockType.None)
                        && (_map[i, j].FogOfWar > 0f))
                    {
                        Vector2 blockPos = new Vector2(
                            (i * BLOCK_SIZE) + (BLOCK_SIZE / 2), 
                            (j * BLOCK_SIZE) + (BLOCK_SIZE / 2));

                        float dist = (float)Metrics.GetDistance(_player.Position, blockPos);
                        if (dist <= _player.SightRange)
                        {
                            _map[i, j].FogOfWar = 0;
                        }
                        else if (dist <= (_player.SightRange + GOPlayer.AttenuationOfSight))
                        {
                            float newFogOfWar = (1f/GOPlayer.AttenuationOfSight)
                                                *(dist - _player.SightRange);

                            if (_map[i, j].FogOfWar > newFogOfWar)
                                _map[i, j].FogOfWar = newFogOfWar;
                        }
                    }
                }
            }
        }
    }
}
